/*
 *  Copyright (C) 2010 Zubanov Dmitry
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
/**
 *
 * @author Zubanov Dmitry aka javadimon, zubanov@gmail.com
 * Omsk, Russia, created 29.05.2010
 * (C) Copyright by Zubanov Dmitry
 */
package org.jssdb.core.josql;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jssdb.core.handler.FileStorageHandler;
import org.jssdb.core.ram.RamHandler;

public class OQLHandler {

    private static OQLHandler self = null;
    private RamHandler ramHandler = RamHandler.getInstance();
    private static final Logger logger = Logger.getLogger(OQLHandler.class.getName());

    private OQLHandler() {
        //
    }

    public static OQLHandler getInstance() {
        if (self == null) {
            self = new OQLHandler();
        }

        return self;
    }

    public void setIdOffset(int autoStart, int autoIncrement, int autoOffset) {
        FileStorageHandler.getDefault().setIdOffset(autoStart, autoIncrement, autoOffset);
    }

    public ArrayList<Map> getResultSet(Map selectMap, boolean isNormalization) {
        ArrayList<Map> resultArray = new ArrayList();

        // select handler
        ArrayList<Map<String, String>> aSelect = (ArrayList<Map<String, String>>) selectMap.get("select");
        if (aSelect == null) {
            return null;
        }

        for (Map<String, String> m : aSelect) {
            String alias = (String) m.keySet().toArray()[0];
            String className = m.get(alias);
            Map<String, ArrayList<Map<Long, Object>>> mResult = new HashMap();

            if (isClass(className)) {
                mResult.put(alias, ramHandler.getCollection(className, true));
                resultArray.add(mResult);

            } else {
                String clsName = getClassName(className);
                if (clsName == null) {
                    continue;
                }

                String methodName = getMethodName(className);

                ArrayList aResult = new ArrayList();
                ArrayList<Map<Long, Object>> a = ramHandler.getCollection(clsName, true);
                for (Map<Long, Object> mObj : a) {
                    long key = (Long) mObj.keySet().toArray()[0];
                    Object obj = mObj.get(key);
                    Class cls = obj.getClass();
                    for (Method method : cls.getMethods()) {
                        if (!method.getName().equals(methodName)) {
                            continue;
                        }
                        try {
                            Object r = method.invoke(obj, new Object[]{});
                            Map<Long, Object> mr = new HashMap();
                            mr.put(key, r);
                            aResult.add(mr);
                        } catch (Exception e) {
                            logger.log(Level.SEVERE, alias, e);
                        }
                    }
                }

                mResult.put(alias, aResult);
                resultArray.add(mResult);
            }

        }

        // where handler
        if (selectMap.get("where") != null) {
            WherePrepare wh = new WherePrepare();
            wh.prepare(resultArray, selectMap);
            resultArray = new WhereHandler().getResultArray(resultArray, wh.getArrayTree());
        }

        // order handler
        if (((Map) selectMap.get("order")).containsKey("isASC")) {
            resultArray = orderHandler(resultArray, selectMap);
        }

        // limit handler
        if (((Map) selectMap.get("limit")).containsKey("count")) {
            resultArray = limitHandler(resultArray, selectMap);
        }

        if(isNormalization){
            return normalization(resultArray);
        }
        
        return resultArray;
    }

    private ArrayList<Map> normalization(ArrayList<Map> resultSet) {

        ArrayList<Map> rs = new ArrayList();
        boolean isFirst = true;

        for (Map m : resultSet) {
            String alias = (String) m.keySet().toArray()[0];
            ArrayList<Map<Long, Object>> resultArray = (ArrayList<Map<Long, Object>>) m.get(alias);
            if (resultArray == null) {
                logger.info("result array is null");
                continue;
            }

            int row = 0;
            for (Map mr : resultArray) {
                Map<String, Object> resultMap = new LinkedHashMap();

                if (mr == null) {
                    logger.info("null");
                    continue;
                }
                Object key = mr.keySet().toArray()[0];
                logger.log(Level.INFO, "{0}, {1}, {2}", new Object[]{alias, key, mr.get(key)});

                if (isFirst) {
                    resultMap.put("id", key);
                    resultMap.put(alias, mr.get(key));
                    rs.add(resultMap);
                } else {
                    rs.get(row).put(alias, mr.get(key));
                }
                row++;
            }
            isFirst = false;
        }

        return rs;
    }

    private ArrayList<Map> orderHandler(ArrayList<Map> resultArray, Map selectMap) {
        Map mOrder = (Map) selectMap.get("order");
        String strClassName = "" + mOrder.get("class");
        final boolean isASC = (Boolean) mOrder.get("isASC");

        String arr[] = strClassName.split("\\.");
        String className = "";
        if (arr.length > 1) {
            for (int i = 0; i < arr.length - 1; i++) {
                className += arr[i] + ".";
                className = className.substring(0, className.length() - 1);
            }
        } else {
            className = strClassName;
        }

        String mArr[] = arr[arr.length - 1].split(" ");
        final String methodName = mArr[0];

        // handle
        ArrayList<Map> resultArrayOrder = (ArrayList) resultArray.clone();
        resultArray.clear();

        for (int i = 0; i < resultArrayOrder.size(); i++) {
            Map<String, Object> m = resultArrayOrder.get(i);
            String key = "" + m.keySet().toArray()[0];
            ArrayList a = (ArrayList) m.get(key);
            System.out.println("a = " + a);

            Collections.sort(a, new Comparator() {

                public int compare(Object o1, Object o2) {
                    int result = 0;

                    Object val1 = null, val2 = null;
                    Map mo1 = (Map) o1;
                    Object obj1 = mo1.get(mo1.keySet().toArray()[0]);
                    Map mo2 = (Map) o2;
                    Object obj2 = mo2.get(mo2.keySet().toArray()[0]);

                    if (!isGeneric(obj1)) {
                        Class cls = obj1.getClass();
                        for (Method method : cls.getMethods()) {
                            if (!method.getName().equals(methodName)) {
                                continue;
                            }

                            try {
                                val1 = method.invoke(obj1, new Object[]{});
                            } catch (Exception e) {
                                logger.log(Level.SEVERE, methodName, e);
                            }
                        }

                        cls = obj2.getClass();
                        for (Method method : cls.getMethods()) {
                            if (!method.getName().equals(methodName)) {
                                continue;
                            }

                            try {
                                val2 = method.invoke(obj2, new Object[]{});
                            } catch (Exception e) {
                                logger.log(Level.SEVERE, methodName, e);
                            }

                        }
                    } else {
                        val1 = mo1.get(mo1.keySet().toArray()[0]);
                        val2 = mo2.get(mo2.keySet().toArray()[0]);
                    }

                    if (val1 instanceof Number) {
                        result = new CompareResultNumber().getResult(val1, val2, isASC);
                    }

                    if (val1 instanceof String) {
                        if (isASC) {
                            result = ("" + val1).compareToIgnoreCase("" + val2);
                        } else {
                            result = ("" + val1).compareToIgnoreCase("" + val2) * -1;
                        }
                    }

                    if (val1 instanceof java.util.Date) {
                        result = new CompareResultDate().getResult((Date) val1, (Date) val2, isASC);
                    }

                    return result;
                }
            });

            Map<String, Object> mOut = new HashMap();
            mOut.put(key, a);
            resultArray.add(mOut);
        }

        return resultArray;
    }

    private boolean isGeneric(Object obj) {
        boolean result = false;

        if (obj == null) {
            return result;
        }

        if (obj instanceof Boolean) {
            result = true;
        }

        if (obj instanceof Number) {
            result = true;
        }

        if (obj instanceof String) {
            result = true;
        }

        if (obj instanceof java.util.Date) {
            result = true;
        }

        return result;
    }

    private ArrayList<Map> limitHandler(ArrayList<Map> resultArray, Map selectMap) {
        Map mLimit = (Map) selectMap.get("limit");
        long count = (Long) mLimit.get("count");
        long offset = (Long) mLimit.get("offset");
        ArrayList<Map> resultArrayLimit = (ArrayList) resultArray.clone();
        resultArray.clear();

        for (int i = 0; i < resultArrayLimit.size(); i++) {
            Map<String, Object> m = resultArrayLimit.get(i);
            String key = "" + m.keySet().toArray()[0];
            ArrayList a = (ArrayList) m.get(key);

            boolean isAdd = false;
            long counter = 0;
            ArrayList aOut = new ArrayList();
            for (int j = 0; j < a.size(); j++) {
                if (offset == j) {
                    isAdd = true;
                }

                if (isAdd) {
                    aOut.add(a.get(j));
                    counter++;
                }

                if (counter == count) {
                    isAdd = false;
                }
            }

            Map<String, Object> mOut = new HashMap();
            mOut.put(key, aOut);
            resultArray.add(mOut);
        }

        return resultArray;
    }

    private String getMethodName(String className) {
        String arr[] = className.split("\\.");
        return arr[arr.length - 1];
    }

    private String getClassName(String className) {
        String result = "";
        String arr[] = className.split("\\.");
        for (int i = 0; i < arr.length - 1; i++) {
            result = result + arr[i] + ".";
        }

        result = result.substring(0, result.length() - 1);

        if (!isClass(result)) {
            result = null;
        }

        return result;
    }

    private boolean isClass(String className) {
        boolean result = true;
        try {
            Class cls = Class.forName(className);
        } catch (ClassNotFoundException ex) {
            result = false;
        }
        return result;
    }

    public void addObject(Object obj) {
        ramHandler.addObject(obj);
    }

    public void updateObject(int id, Object obj) {
        ramHandler.updateObject(id, obj);
    }

    public void deleteObject(int id, String className) {
        ramHandler.deleteObject(id, className);
    }
}
